Route byte-level llama tokenizers to TokenizersBackend#47017
Conversation
bf64c6f to
e073da1
Compare
|
This has been an issue since 5.3.0 I believe, would be great to get this fixed. |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
Thanks for the review @itazap! (and thanks @qflen for the redirect 🙏) Thanks for flagging the redundant work — you're right that On the pure
Note the official What I'd propose instead — a hybrid that keeps the authoritative decision but kills the "parse on every llama load" cost: if (
tokenizer_auto_map is None
and TokenizersBackend is not None
and config_model_type in _DUAL_SCHEME_MODEL_TYPES
and not has_file(pretrained_model_name_or_path, "tokenizer.model", **kwargs) # SP Llama-1/2 short-circuits, no parse
and _tokenizer_json_is_byte_level(pretrained_model_name_or_path, **kwargs) # confirm byte-level by content
):
return TokenizersBackend.from_pretrained(...)So Llama-1/2 loads only do a cheap Separately, I can drop the double-parse by threading the already-parsed json (or resolved path) into Does the hybrid sound reasonable to you, or would you prefer the simpler existence check and accept the edge cases? @itazap the 7 red checks look unrelated to this PR (which only touches
Both came in with the main merge, not the diff — could you re-run once main is green? Thanks! |
Model type "llama" spans both SentencePiece (Llama-1/2) and byte-level (Llama-3 / tiktoken) tokenizers under one Hub tokenizer_class (LlamaTokenizerFast). In v5, LlamaTokenizer.__init__ unconditionally installs a Metaspace pre-tokenizer/decoder, which silently drops spaces for byte-level repos (see huggingface#45488), e.g. deepseek-ai/DeepSeek-R1-Distill-Llama-*. The existing MODEL_IDS_TO_TOKENIZERS_BACKEND allowlist only covers specific checkpoints (e.g. the 8B) and misses others (the 70B is still broken). Instead, for the small set of dual-scheme model types, inspect the serialized tokenizer.json: if it declares a ByteLevel pre_tokenizer/decoder, route to TokenizersBackend (which respects tokenizer.json). SentencePiece Llama-1/2 stays on LlamaTokenizer unchanged.
138ee16 to
b0f5a9f
Compare
Quantization pipelines copy the SentencePiece tokenizer.model over from the checkpoint they were derived from, so byte-level llama repos that declare LlamaTokenizer and ship a root tokenizer.model exist in the wild (e.g. benyamini/DeepSeek-R1-Distill-Llama-8B-AWQ-w4g128). Routing on the presence of tokenizer.model sends those to LlamaTokenizer, whose Metaspace pre-tokenizer drops spaces, so pin that the decision is made from tokenizer.json.
|
Following up on my own proposal above, @itazap — I tested the hybrid before pushing it, and it doesn't hold. The The edge I described above as hypothetical turns out to be real:
I also mis-diagnosed the cost above: it's one Hub round-trip, not the parse. Warm cache on So I'd keep the routing logic as it stands. I've pushed a no-network regression test pinning the case above — it fails on |
|
[For maintainers] Suggested jobs to run (before merge) run-slow: auto |
CI recapDashboard: View test results in Grafana |
Model type "llama" spans both SentencePiece (Llama-1/2) and byte-level (Llama-3 / tiktoken) tokenizers under one Hub tokenizer_class (LlamaTokenizerFast). In v5, LlamaTokenizer.init unconditionally installs a Metaspace pre-tokenizer/decoder, which silently drops spaces for byte-level repos (see #45488), e.g. deepseek-ai/DeepSeek-R1-Distill-Llama-*.
The existing MODEL_IDS_TO_TOKENIZERS_BACKEND allowlist only covers specific checkpoints (e.g. the 8B) and misses others (the 70B is still broken). Instead, for the small set of dual-scheme model types, inspect the serialized tokenizer.json: if it declares a ByteLevel pre_tokenizer/decoder, route to TokenizersBackend (which respects tokenizer.json). SentencePiece Llama-1/2 stays on LlamaTokenizer unchanged.
What does this PR do?
model_type == "llama"covers two incompatible tokenizer schemes under a single Hubtokenizer_class(
LlamaTokenizerFast):▁)deepseek-ai/DeepSeek-R1-Distill-Llama-*) — byte-level BPE (GPT-2 / tiktoken,Ġ)In v5,
LlamaTokenizer.__init__unconditionally installs aMetaspacepre-tokenizer/decoder, overwriting whatevertokenizer.jsondeclares. For byte-level repos this silently drops spaces on both encode and decode(
"Hello world"→"Helloworld"), which is a silent accuracy regression — see #45488.The current mitigation (
MODEL_IDS_TO_TOKENIZERS_BACKEND, from #46091) is a per-checkpoint allowlist. It fixesdeepseek-ai/deepseek-r1-distill-llama-8bbut misses others — for exampleDeepSeek-R1-Distill-Llama-70Bisstill broken on
main(it is not in the list).Instead of enumerating checkpoints, this PR adds a small content-based rule: for the (rare) set of dual-scheme
model types (
{"llama"}), inspect the serializedtokenizer.json; if it declares aByteLevelpre-tokenizer/decoder, route to
TokenizersBackend(which respectstokenizer.json). SentencePiece Llama-1/2(no
ByteLevelintokenizer.json) keeps usingLlamaTokenizerunchanged.LlamaTokenizer.__init__is notmodified, so there is no risk to the SentencePiece path.
This covers any byte-level Llama repo (8B, 70B, future distills) without maintaining a checkpoint allowlist.
Fixes #45488 for Llama-3-derived checkpoints.
Verification
Round-trip on
"Hello world.\nI'm an AI, so I don't have consciousness."(transformersmain):main)DeepSeek-R1-Distill-Llama-70B(not in allowlist)LlamaTokenizer, roundtrip False ("Helloworld.I'manAI.")TokenizersBackend, roundtrip TrueDeepSeek-R1-Distill-Llama-8BTokenizersBackend, TrueTokenizersBackend, TrueNousResearch/Llama-2-7b-hfLlamaTokenizer, TrueLlamaTokenizer, True (unchanged)A no-network unit test for the detection helper is added in
tests/models/auto/test_tokenization_auto.py::AutoTokenizerTest::test_tokenizer_json_is_byte_level.